﻿using System.Text;
using MicroMVVM.Services.Interfaces;
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Windows.Storage;
using Windows.Storage.Streams;

namespace MicroMVVM.Services.Implementation.Any
{
    [ExportService(ServiceType.Any, typeof (IStorageFileService), PartCreationPolicy.Singleton)]
    public class StorageFileService : IStorageFileService
    {
        /// <summary>
        /// Fetches a file from the specified location.
        /// </summary>
        /// <param name="filename"></param>
        /// <param name="storageLocation"></param>
        /// <param name="path"></param>
        /// <returns></returns>
        public async Task<StorageFile> FetchFileAsync(string filename,
                                                      StorageLocation storageLocation = StorageLocation.Roaming,
                                                      string path = "")
        {
            var storageFolder = await TranslateStorageLocation(storageLocation, path);

            if (await FileExistsAsync(filename, storageLocation, path))
                return await storageFolder.GetFileAsync(filename);

            var result = await storageFolder.CreateFileAsync(filename, CreationCollisionOption.ReplaceExisting);

            return await storageFolder.GetFileAsync(filename);
        }

        /// <summary>
        /// Checks if a file exists in the specified location.
        /// </summary>
        /// <param name="filename"></param>
        /// <param name="storageLocation"></param>
        /// <param name="path"></param>
        /// <returns></returns>
        public async Task<bool> FileExistsAsync(string filename,
                                                StorageLocation storageLocation = StorageLocation.Roaming,
                                                string path = "")
        {
            try
            {
                var storageFolder = await TranslateStorageLocation(storageLocation, path);

                var files = await storageFolder.GetFilesAsync();

                return files.Any(storageFile => storageFile.Name == filename);
            }
            catch (Exception)
            {
                return false;
            }
        }

        /// <summary>
        /// Reads the file from the specified location.
        /// </summary>
        /// <param name="filename"></param>
        /// <param name="storageLocation"></param>
        /// <param name="path"></param>
        /// <returns></returns>
        public async Task<byte[]> ReadAsync(string filename,
                                            StorageLocation storageLocation = StorageLocation.Roaming,
                                            string path = "")
        {
            try
            {
                var storageFile = await FetchFileAsync(filename, storageLocation, path);

                using (var readStream = await storageFile.OpenAsync(FileAccessMode.Read))
                {
                    using (var dataReader = new DataReader(readStream.GetInputStreamAt(0)))
                    {
                        var size = readStream.Size;

                        if (size <= UInt32.MaxValue)
                        {
                            var bytes = new byte[size];

                            await dataReader.LoadAsync((UInt32) bytes.Length);

                            dataReader.ReadBytes(bytes);

                            return bytes;
                        }

                        //As indicated in MSDN, files larger than 4GB should not be loaded using LoadAsync.
                        throw new InvalidDataException(
                            string.Format("File {0} is too big for LoadAsync to load in a single chunk.", filename));
                    }
                }
            }
            catch (Exception)
            {
                return null;
            }
        }

        /// <summary>
        /// Writes to the specified file in the specified location.
        /// </summary>
        /// <param name="filename"></param>
        /// <param name="content"></param>
        /// <param name="storageLocation"></param>
        /// <param name="path"></param>
        /// <returns></returns>
        public async Task<bool> WriteAsync(string filename,
                                           byte[] content,
                                           StorageLocation storageLocation = StorageLocation.Roaming,
                                           string path = "")
        {
            try
            {
                using (var memoryRandomAccessStream = new InMemoryRandomAccessStream())
                {
                    using (var dataWriter = new DataWriter(memoryRandomAccessStream))
                    {
                        dataWriter.WriteBytes(content);

                        var buffer = dataWriter.DetachBuffer();

                        var storageFile = await FetchFileAsync(filename, storageLocation, path);

                        await FileIO.WriteBufferAsync(storageFile, buffer);

                        return true;
                    }
                }
            }
            catch (Exception)
            {
                return false;
            }
        }

        /// <summary>
        /// Writes to the specified file in the specified location.
        /// </summary>
        /// <param name="filename"></param>
        /// <param name="content"></param>
        /// <param name="storageLocation"></param>
        /// <param name="path"></param>
        /// <param name="encoding"></param>
        /// <returns></returns>
        public async Task<bool> WriteAsync(string filename,
                                           string content,
                                           StorageLocation storageLocation = StorageLocation.Roaming,
                                           string path = "",
                                           Encoding encoding = null)
        {
            encoding = encoding ?? Encoding.UTF8;

            var bytes = encoding.GetBytes(content);

            return await WriteAsync(filename, bytes, storageLocation, path);
        }

        /// <summary>
        /// Translates the storage location enum to storage folders.
        /// </summary>
        /// <param name="storageLocation"></param>
        /// <param name="path"></param>
        /// <returns></returns>
        private static async Task<StorageFolder> TranslateStorageLocation(StorageLocation storageLocation,
                                                                          string path = "")
        {
            try
            {
                StorageFolder storageFolder;

                switch (storageLocation)
                {
                    case StorageLocation.Local:
                        storageFolder = ApplicationData.Current.LocalFolder;
                        break;

                    case StorageLocation.Temporary:
                        storageFolder = ApplicationData.Current.TemporaryFolder;
                        break;

                    case StorageLocation.InstalledLocation:
                        storageFolder = Windows.ApplicationModel.Package.Current.InstalledLocation;
                        break;

                    default:
                        storageFolder = ApplicationData.Current.RoamingFolder;
                        break;
                }

                if (string.IsNullOrEmpty(path) == false)
                    storageFolder = await storageFolder.GetFolderAsync(path);

                return storageFolder;
            }
            catch (Exception)
            {
                return null;
            }
        }
    }
}